package cn.skyquant.quant4j.jforex.strategy.asura;

import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.api.forex.ForexConfigSeriesDTO;
import cn.skyquant.quant4j.api.forex.ForexServiceProxy;
import cn.skyquant.quant4j.api.goods.GoodsDTO;
import cn.skyquant.quant4j.api.quant.TimeBar;
import cn.skyquant.quant4j.api.ylzc.YlzcDTO;
import cn.skyquant.quant4j.api.ylzc.YlzcMap;
import cn.skyquant.quant4j.jforex.sdk.enums.LabelType;
import cn.skyquant.quant4j.jforex.sdk.enums.OpenType;
import cn.skyquant.quant4j.jforex.sdk.enums.YlzcType;
import cn.skyquant.quant4j.jforex.sdk.strategy.*;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseConfig;
import cn.skyquant.quant4j.jforex.sdk.util.QuantUtil;
import cn.skyquant.quant4j.sdk.enums.CloseType;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 阿修罗
 * 动态点位逆市加仓策略
 *
 * @author harley-dog
 */
@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class Asura045 extends BaseStrategy implements IBarFeedListener {
    @Configurable("基础净值（★重要★）")
    public int basic_equity = 20000;
    @Configurable("逆市是否开首仓")
    public boolean R_Trade = false;
    @Configurable("停盘X分钟内不运行EA")
    public long offline_time = 30;
    @Configurable("加仓数列列表")
    public int seriesId = 113;
    @Configurable("大K柱计算个数")
    public int atr_days = 180;
    @Configurable("抗单距离X倍大ATR")
    public double R_atr_max_n = 6.5;
    @Configurable("止损值为几倍大K柱ATR")
    public double stop_loss_atr = 0.5;
    @Configurable("订单大于X止损")
    public int stop_money = -1000;
    @Configurable("移动止损-止盈变止损")
    public boolean tp_change_to_sl = true;
    @Configurable("移动止损-吊灯:尾仓后最高点下方x倍ATR")
    public int diaodeng = 4;
    @Configurable("移动止损-YOYO:上根小K柱高点下方y倍ATR")
    public int yoyo = 2;
    @Configurable("开单允许最大滑点")
    public double slippage = 5;
    @Configurable("小K线ATR计算个数")
    public int R_atr_K = 4;
    @Configurable("首仓距离压力支撑位间隔X倍大K柱波动率")
    public double first_loss_limit_position = 0.3;
    @Configurable("压力支撑位计算方法")
    public YlzcType ylzcType = YlzcType.AI2;
    @Configurable("大K柱")
    public Period calsPeriod = Period.DAILY;
    @Configurable("电脑1计算压力支撑K柱个数(单数)")
    public int v_days = 7;
    @Configurable("电脑1计算压力支撑最大反转")
    public int AI_MAX_k = 0;
    @Configurable("电脑1计算压力支撑取最低最高价")
    public boolean AI_LIMIT = true;
    @Configurable("开启压力支撑位反转")
    public boolean AI_ylzc_R = true;
    @Configurable("上一次止损后必须产生新支撑压力位")
    public boolean last_sl_new_ylzc = true;
    @Configurable("复利开单")
    public boolean compound = true;
    @Configurable("复利值不能小于1")
    public boolean compound_1 = true;
    @Configurable("从最低位回调")
    public boolean tp_maxmin = true;
    @Configurable("加仓在最低价")
    public boolean add_maxmin = true;
    @Configurable("每次加仓间隔一根小K柱")
    public boolean jiange = true;
    @Configurable("反向加倍")
    public String reflect_str = "6:2,10:3";
    @Configurable("1:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str0 = "EURUSD,A,1,3,0.3";
    @Configurable("2:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str1 = "GBPUSD,B,1,3,0.3";
    @Configurable("3:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str2 = "USDCAD,S,1,3,0.3";
    @Configurable("4:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str3 = "";
    @Configurable("5:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str4 = "";
    @Configurable("6:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str5 = "";
    @Configurable("7:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str6 = "";
    @Configurable("8:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str7 = "";
    @Configurable("9:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str8 = "";
    @Configurable("10:货币,方向,倍率,K线ATR,大K柱ATR")
    public String str9 = "";
    @Configurable("订单成交日志")
    public boolean log_order_fill = true;
    @Configurable("订单平仓日志")
    public boolean log_order_close = true;
    @Configurable("订单提交日志")
    public boolean log_order_submit = true;
    @Configurable("订单合并日志")
    public boolean log_order_merge = false;
    @Configurable("订单取消日志")
    public boolean log_order_cancel = false;
    @Configurable("首仓调试日志")
    public boolean log_first = false;
    @Configurable("压力支撑位日志")
    public boolean log_ylzc = false;
    @Configurable("ATR,布林带日志")
    public boolean log_daily_open = false;
    @Configurable("不开单原因日志")
    public boolean log_order_reason = false;
    @Configurable("移动止损止盈日志")
    public boolean log_order_move = false;

    ForexServiceProxy forexServiceProxy;
    Series series;//通用加仓数列
    ITimeDomain timeDomain;//下一个停盘时间
    ReflectManager reflectManager;//反向加仓管理

    final SimpleDateFormat dateFormat_yymmddhhmm = new SimpleDateFormat("yyyy-MM-dd HH:mm");

    final Map<Instrument, Long> dailyDateMap = new HashMap<>();//记录开盘时间
    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体
    final Map<Instrument, Double> atrMap = new HashMap<>();//大K柱ATR
    final Map<Instrument, YlzcMap> ylzcMap = new HashMap<>();//压力支撑位
    final Map<String, IOrder> lastCloseOrder = new HashMap<>();//记录上一个平仓的订单
    final Map<Instrument, OfferSide> offerSideMap = new HashMap<>();
    final Map<Instrument, List<TimeBar>> askBarMap = new HashMap<>();
    final Map<Instrument, List<TimeBar>> bidBarMap = new HashMap<>();
    final Map<String, String> submitStr = new HashMap<>();
    final Map<String, String> submitStr1 = new HashMap<>();
    final Map<String, String> submitStr2 = new HashMap<>();
    double tp_sum = 0;
    double sl_sum = 0;

    @Override
    public final void onStopEx() {
        super.onStopEx();
        forexServiceProxy = null;
        series = null;
        timeDomain = null;
        reflectManager = null;
        dailyDateMap.clear();
        insMap.clear();
        atrMap.clear();
        ylzcMap.clear();
        lastCloseOrder.clear();
        offerSideMap.clear();
        askBarMap.clear();
        bidBarMap.clear();
        submitStr1.clear();
        submitStr.clear();
    }

    @Override
    protected String getVersion() {
        return "045";
    }

    @Override
    protected String getName() {
        return "Asura" + eaId;
    }

    @Override
    protected String getEaName() {
        return "Asura";
    }

    @Override
    public void onTickEx(Instrument instrument, ITick tick) {
    }

    @Override
    public void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {
    }

    //初始化方法
    @Override
    public void init(InitEntity initEntity) {
        forexServiceProxy = new ForexServiceProxy(account.getAccountId(),BorkerType.DUKASCOPY);
        reflectManager = new ReflectManager(reflect_str);
        String[] strArr = {str0, str1, str2, str3, str4, str5, str6, str7, str8, str9};
        for (int i = 0; i < strArr.length; i++) {
            if (strArr[i] != null && strArr[i].trim().length() > 0) {
                String[] s = strArr[i].split(",");
                if (s.length == 5
                        && s[0].trim().length() > 0
                        && s[1].trim().length() > 0
                        && s[2].trim().length() > 0
                        && s[3].trim().length() > 0
                        && s[4].trim().length() > 0) {
                    Instrument instrument = Instrument.valueOf(s[0]);
                    if (instrument == null) {
                        if (log_order_reason) warn("can't trade");
                        continue;
                    }
                    ResultDTO<GoodsDTO> resultDTO = goodsService.get(instrument.name(), BorkerType.DUKASCOPY);
                    if (!resultDTO.okData()) {
                        if (log_order_reason)
                            warn("%s must be config ,can't trade:%s", instrument.getName(), resultDTO.message);
                        continue;
                    }
                    GoodsDTO goodsDTO = resultDTO.data;
                    if (!insMap.containsKey(instrument)) {
                        insMap.put(instrument, new InstrumentEntity(instrument));
                    }
                    double diff = goodsDTO.avgSpread;
                    try {
                        String tradeDirection = s[1];
                        Double multiple = Double.parseDouble(s[2]);
                        Double k_atr = Double.parseDouble(s[3]);
                        Double d_atr = Double.parseDouble(s[4]);
                        //逆市
                        AsuraConfig a = new AsuraConfig(getName(), i, multiple, 0, instrument, diff, k_atr, d_atr, tradeDirection);//逆趋势
                        initEntity.a.add(a);
                        //货币组
                        initEntity.b.add(instrument);
                        if (log_start_stop) {
                            out(a.toString());
                        }
                        offerSideMap.put(instrument, a.tradeDirection == TradeDirection.LONG ? OfferSide.ASK : OfferSide.BID);
                    } catch (NumberFormatException e) {
                        initEntity.addReason("init " + i + "config error:" + e);
                    }
                } else {
                    if (log_start_stop)
                        warn("config[%d] must be 5 params[symbol,direction,rate,k_atr,d_atr],but it find %d params", i, s.length);
                }
            }
        }
        if (initEntity.a.isEmpty()) {
            initEntity.addReason("Asura config is empty");
        }
        series = new Series(seriesId);
        IBarFeedListener listener = this;
        initEntity.b.forEach(x -> {
            OfferSide offerSide = offerSideMap.get(x);
            context.subscribeToBarsFeed(x, period, offerSide, listener);//逆市运行间隔
        });
        initEntity.r = true;
        //启动日志
        if (super.log_start_stop) {
            try {
                //遍历所有订单
                List<IOrder> orderList = engine.getOrders();
                for (BaseConfig baseConfig : initEntity.a) {
                    if (!baseConfig.isValid()) {
                        continue;
                    }
                    if (baseConfig instanceof AsuraConfig) {
                        AsuraConfig c = (AsuraConfig) baseConfig;
                        OrderExManager o = getOrderExManager(c, orderList, AsuraOrderComment.class);
                        if (o.buy_last != null) {
                            out("%s-[%d], %s, LONG  order.index=%d , amount=%s, last_price = %s, TP = %s , SL = %s , next_limit_price=%s ",
                                    c.name, c.index, c.instrument.name(),
                                    o.buy_last.seriesIndex,
                                    o.buy_last.order.getAmount(),
                                    ((AsuraOrderComment) o.buy_last.orderComment).pre_open_price,
                                    o.buy_last.order.getTakeProfitPrice(),
                                    o.buy_last.order.getStopLossPrice(),
                                    o.buyLimitList.isEmpty() ? 0 : o.buyLimitList.get(0).order.getOpenPrice());
                        } else {
                            out("%s-[%d], %s, LONG  has no order", c.name, c.index, c.instrument.name());
                        }
                        if (o.sell_last != null) {
                            out("%s-[%d], %s, SHORT order.index=%d , amount=%s, last_price = %s, TP = %s , SL = %s , next_limit_price=%s ",
                                    c.name, c.index, c.instrument.name(),
                                    o.sell_last.seriesIndex,
                                    o.sell_last.order.getAmount(),
                                    ((AsuraOrderComment) o.sell_last.orderComment).pre_open_price,
                                    o.sell_last.order.getTakeProfitPrice(),
                                    o.sell_last.order.getStopLossPrice(),
                                    o.sellLimitList.isEmpty() ? 0 : o.sellLimitList.get(0).order.getOpenPrice());
                        } else {
                            out("%s-[%d], %s, SHORT  has no order", c.name, c.index, c.instrument.name());
                        }
                    }
                }
            } catch (JFException e) {
                err("init error:", e.getMessage());
            }
        }
    }

    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {

        try {
            ITimeDomain timeDomain0;
            ITimeDomain timeDomain1;
            try {
                timeDomain0 = dataService.getOfflineTimeDomain(0);
                timeDomain1 = dataService.getOfflineTimeDomain(-1);
            } catch (JFException e) {
                if (log_error) warn("onBar.getOfflineTimeDomain error:%s", e);
                return;
            }
            if (timeDomain0 == null || timeDomain1 == null) {
                if (log_error) warn("onBar.getOffline,TimeDomain is null");
                return;
            }
            long time = bar.getTime();
            //停盘时间不干活
            if (timeDomain1.getEnd() + offline_time * 60000 > time || time > timeDomain0.getStart() - offline_time * 60000) {
                return;
            }
            timeDomain = timeDomain0;

            long barstart = 0;
            try {
                barstart = history.getBarStart(calsPeriod, time);
            } catch (JFException e) {
                return;
            }
            if (barstart == 0) {
                return;
            }
            Long date = dailyDateMap.get(instrument);
            if (date == null || barstart >= date.longValue() + calsPeriod.getInterval()) {
                dailyDateMap.put(instrument, barstart);
                dailyOpen(instrument, time);
            }

            //逆势部分
            if (period.equals(this.period)) {
                configMap.entrySet().stream()
                        .filter(x -> x.getValue().isValid()
                                && x.getValue() instanceof AsuraConfig
                                && getName().equals(((AsuraConfig) x.getValue()).name)
                                && ((AsuraConfig) x.getValue()).instrument == instrument)
                        .forEach(x -> Rprocess((AsuraConfig) x.getValue(), bar.getTime()));
            }
        } catch (Throwable e) {
            for (StackTraceElement se : e.getStackTrace()) {
                err("111error %s", se.toString());
            }
        }
    }

    //开盘时要干的事儿
    private void dailyOpen(Instrument instrument, long time) {
        calcBar(instrument, time);//计算K柱
        calcATR(instrument, calsPeriod, time);//计算波动率
        calcYlzc(instrument, time);//计算压力支撑位
    }

    private void calcBar(Instrument instrument, long time) {
        try {
            long barstart = history.getBarStart(calsPeriod, time);
            List<IBar> askbarList = history.getBars(instrument, calsPeriod, OfferSide.ASK, Filter.WEEKENDS, atr_days, barstart, 0);//最多计算200根K柱
            List<IBar> bidibarList = history.getBars(instrument, calsPeriod, OfferSide.BID, Filter.WEEKENDS, atr_days, barstart, 0);//最多计算200根K柱
            List<TimeBar> asktimeBarList = QuantUtil.batchConvertIBarToTimeBar(askbarList);
            List<TimeBar> bidtimeBarList = QuantUtil.batchConvertIBarToTimeBar(bidibarList);
            askBarMap.put(instrument, asktimeBarList);
            bidBarMap.put(instrument, bidtimeBarList);
        } catch (JFException e) {
            err("calcBar error:%s", e.getMessage());
        }
    }

    //计算ATR
    private void calcATR(Instrument instrument, Period period, long time) {
        InstrumentEntity ie = insMap.get(instrument);
        double[] atrs = null;
        try {
            atrs = indicators.atr(instrument, period, offerSideMap.get(instrument), atr_days, Filter.WEEKENDS, 1, time, 0);
        } catch (JFException e) {
            if (log_error) warn("%s get atr error:%s", instrument.name(), e);
        }
        if (atrs != null && atrs.length > 0 && atrs[0] != 0) {
            atrMap.put(instrument, atrs[0]);
            if (log_daily_open) out("%s.%d days atr=%s", instrument, atr_days, ie.formatPrice(atrs[0]));
        }
    }

    //计算压力支撑位
    private void calcYlzc(Instrument instrument, long time) {
        //找前一天的压力支撑位
        YlzcMap ym = getYlzc(instrument);
        if (ym != null) {
            ylzcMap.put(instrument, ym);
            if (log_ylzc) out("%s.ylw=%s", instrument.name(), ym.ylw);
            if (log_ylzc) out("%s.zcw=%s", instrument.name(), ym.zcw);
        } else {
            if (log_ylzc) out("ylzc is null today");
        }
    }

    private YlzcMap getYlzc(Instrument instrument) {
        YlzcMap ylzcMap = new YlzcMap();
        try {
            if (askBarMap.containsKey(instrument)) {
                List<TimeBar> timeBarList = askBarMap.get(instrument);
                int size = timeBarList.size();
                for (int i = 2; i < size - 2; i++) {
                    TimeBar timeBar = timeBarList.get(i);
                    boolean rv = false;
                    switch (ylzcType) {
                        case AI1:
                            rv = TimeBar.isRV1(timeBarList, i, v_days);
                            break;
                        case AI2:
                            rv = TimeBar.isRV2(timeBarList, i, v_days);
                            break;
                    }
                    if (rv) {
                        YlzcDTO ylzcDTO = new YlzcDTO(instrument.name(), (int) (calsPeriod.getInterval() / 1000), new Date(timeBar.time), cn.skyquant.quant4j.sdk.enums.YlzcType.Y, 5, timeBar.high);
                        ylzcMap.ylw.add(ylzcDTO);
                    }
                }
            }
            if (bidBarMap.containsKey(instrument)) {
                List<TimeBar> timeBarList = bidBarMap.get(instrument);
                int size = timeBarList.size();
                for (int i = 2; i < size - 2; i++) {
                    TimeBar timeBar = timeBarList.get(i);
                    boolean v = false;
                    switch (ylzcType) {
                        case AI1:
                            v = TimeBar.isV1(timeBarList, i, v_days);
                            break;
                        case AI2:
                            v = TimeBar.isV2(timeBarList, i, v_days);
                            break;
                    }
                    if (v) {
                        YlzcDTO ylzcDTO = new YlzcDTO(instrument.name(), (int) (calsPeriod.getInterval() / 1000), new Date(timeBar.time), cn.skyquant.quant4j.sdk.enums.YlzcType.Z, 5, timeBar.low);
                        ylzcMap.zcw.add(ylzcDTO);
                    }
                }
            }
        } catch (Exception e) {
            err("getYlzc error:%s", e.getLocalizedMessage());
        }
        return ylzcMap;
    }

    //开始运行
    private void Rprocess(AsuraConfig x, long time) {
        //先做第一遍过滤，过滤太长的时间，过滤点差,过滤找不到压力支撑位，ATR，布林值等等因素,这些事理论上来说都是不存在的
        Instrument instrument = x.instrument;
        InstrumentEntity ie = insMap.get(instrument);
        if (ie == null) {
            return;
        }
        if (!atrMap.containsKey(instrument) || atrMap.get(instrument) == null) {
            if (log_order_reason) out("%s can't find atr with %s", instrument.name(), atrMap.toString());
        }
        if (!ylzcMap.containsKey(instrument) || ylzcMap.get(instrument) == null) {
            if (log_order_reason) out("%s can't find ylzc with %s", instrument.name(), ylzcMap.toString());
        }
        ITick tick;
        try {
            tick = history.getLastTick(instrument);
        } catch (JFException e) {
            if (log_error) warn("%s get Last Tick error:%s", instrument.name(), e);
            return;
        }
        long time_diff = time - tick.getTime();
        if ((time - tick.getTime()) > 2 * period.getInterval()) {
            if (log_order_reason)
                out("%s,tick time=%s,time.diff=%d s > %d s,can't open order", instrument.name(), CalendarUtils.formatStandard(tick.getTime()), time_diff / 1000, (2 * period.getInterval() / 1000));
            return;
        }

        //找出所有的订单
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            if (log_order_reason) warn("%s getOrders error:%s", instrument, e.getMessage());
            return;
        }
        //生成OrderExManager
        OrderExManager orderExManager = getOrderExManager(x, orderList, AsuraOrderComment.class);
        if (x.tradeDirection == TradeDirection.LONG || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.LONG, orderExManager);
        }
        if (x.tradeDirection == TradeDirection.SHORT || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.SHORT, orderExManager);
        }
    }

    private void RprocessByType(AsuraConfig x, InstrumentEntity ie, ITick tick, TradeDirection tradeDirection, OrderExManager orderExManager) {
        List<OrderEx> orderExList = orderExManager.getOrderExList(tradeDirection);
        List<OrderEx> orderLimitExList = orderExManager.getLimitOrderExList(tradeDirection);
        OrderEx last = orderExManager.getLast(tradeDirection);
        OrderEx first = orderExManager.getFirst(tradeDirection);
        OrderEx reflectLast = orderExManager.getReflectLastOrderEx(tradeDirection);

        //如果有限价挂单,则不做任何处理
        if (!orderLimitExList.isEmpty()) {
            return;
        }

        if (orderExList != null) {
            for (OrderEx orderEx : orderExList) {
                double profit = orderEx.order.getProfitLossInUSD();
                double compoun = compound ? account.getEquity() / basic_equity : 1d;
                if (compound_1 && compoun < 1) {
                    compoun = 1;
                }
                if (profit < stop_money * x.m * compoun) {
                    try {
                        //closeLimit(orderEx);
                        orderEx.order.close();
                        orderEx.order.waitForUpdate(500, IOrder.State.CLOSED);
                        if (log_order_close) {
                            err("%s %s %d ,order.loss[%.2f] < stop_money[%d] * m[%.1f] * compound[%.2f]",
                                    orderEx.instrument.name(), orderEx.tradeDirection.name(), orderEx.seriesIndex, profit, stop_money, x.m, compoun);
                        }
                        return;
                    } catch (JFException e) {

                    }
                }
            }
        }

        //如果空仓则开首仓
        if (orderExList.isEmpty() && R_Trade) {
            //计算暂停时间是否达标
            openFirstOrder(x, tick, tradeDirection, reflectLast);
            return;
        }
        //如果当前有两仓，就不对了，表示没有合并仓位，需要合并仓位
        if (orderExList.size() > 1) {
            merge(x.instrument, ie, x.name, x.index, tradeDirection);
            return;
        }

        //判断有一个正常的仓位
        boolean is_normal = last != null && first != null && last.equals(first) && last.orderComment.type == OpenType.P && last.orderComment instanceof AsuraOrderComment;

        if (is_normal) {
            //成本价以下再加仓
            if ((tradeDirection == TradeDirection.LONG && tick.getAsk() < last.order.getOpenPrice())
                    || (tradeDirection == TradeDirection.SHORT && tick.getBid() > last.order.getOpenPrice())
            ) {
                //如果不是空仓，又没有限价单，则直接开下一仓
                addOrder(x, ie, tradeDirection, tick, last, reflectLast);//加仓
            }
        }

        //修改保本止损
        if (is_normal && diaodeng > 0) {
            if ((tradeDirection == TradeDirection.LONG && tick.getBid() > last.order.getOpenPrice())
                    || (tradeDirection == TradeDirection.SHORT && tick.getAsk() < last.order.getOpenPrice())) {
                try {
                    updateStopLoss(x, ie, tradeDirection, last, tick);
                } catch (JFException e) {
                    err("updateStopLoss error:s", e.getMessage());
                }
            }
        } else if (is_normal && diaodeng == 0 && tp_maxmin) {
            //从最低位回调
            if ((tradeDirection == TradeDirection.LONG && tick.getBid() > last.order.getOpenPrice())
                    || (tradeDirection == TradeDirection.SHORT && tick.getAsk() < last.order.getOpenPrice())) {
                updateTakeProfit(x, ie, tradeDirection, last);
            }
        }
    }

    //移动修改止损
    private void updateStopLoss(
            AsuraConfig x,      //配置
            InstrumentEntity ie, //交易品种属性
            TradeDirection tradeDirection,
            OrderEx<AsuraOrderComment> last,      //尾仓
            ITick tick
    ) throws JFException {
        //一旦开启了保本止损，那么首先要计算的是止盈价格,这里涉及到，是否要计算从最低位回调的功能
        ForexConfigSeriesDTO sm_current = series.list.get(last.seriesIndex);
        Instrument instrument = x.instrument;
        double atr_day = atrMap.get(instrument);
        double atr_k;
        try {
            atr_k = indicators.atr(instrument, this.period, tradeDirection == TradeDirection.LONG ? OfferSide.ASK : OfferSide.BID, this.R_atr_K, 1);//小K线ATR实时计算
        } catch (JFException e) {
            return;
        }
        double tp_price = atr_day * sm_current.backpip / 100;//理论上的回调价格
        double last_open_price = last.orderComment.open_price;//末仓开仓价格
        double lowhigh = tradeDirection == TradeDirection.LONG ? getLowPrice(instrument, last.orderComment.first_time.getTime(), calsPeriod) : getHighPrice(instrument, last.orderComment.first_time.getTime(), calsPeriod);//最高最低价格
        double lowhigh_diaodeng = tradeDirection == TradeDirection.LONG ? getHighPrice(instrument, last.order.getFillTime(), calsPeriod) : getLowPrice(instrument, last.order.getFillTime(), calsPeriod);
        IBar bar_ask = history.getBar(instrument, calsPeriod, OfferSide.ASK, 1);
        IBar bar_bid = history.getBar(instrument, calsPeriod, OfferSide.ASK, 1);
        double diao_sl_price = tradeDirection == TradeDirection.LONG ? lowhigh_diaodeng - diaodeng * atr_k : lowhigh_diaodeng + diaodeng * atr_k;
        double yoyo_sl_price = tradeDirection == TradeDirection.LONG ? bar_ask.getHigh() - yoyo * atr_k : bar_bid.getLow() + yoyo * atr_k;

        //计算理论的回调价格
        //三个价格取最高的(1,回调价,2:吊灯价格,3:yoyo价格

        if (tradeDirection == TradeDirection.LONG) {
            double tp = tp_maxmin ? lowhigh + tp_price : last_open_price + tp_price;//理论上的止盈价
            double max_diaodeng_yoyo = Math.max(diao_sl_price, yoyo_sl_price);//吊灯价和YOYO价格最大值
            double new_sl;
            if(tp_change_to_sl){
                new_sl = new BigDecimal(Math.max(tp, max_diaodeng_yoyo)).setScale(ie.scale + 1, RoundingMode.UP).doubleValue();
            }else{
                new_sl = new BigDecimal(max_diaodeng_yoyo).setScale(ie.scale + 1, RoundingMode.UP).doubleValue();
            }
            if (lowhigh_diaodeng > tp && new_sl > last.order.getStopLossPrice() && new_sl > last.order.getOpenPrice()) {
                if (log_order_move) {
                    double old_profit = (tp-last.order.getOpenPrice()) * last.order.getAmount()*ie.pipMulti*100;
                    double new_profit = (new_sl-last.order.getOpenPrice()) * last.order.getAmount()*ie.pipMulti*100;
                    double profit_diff =  new_profit - old_profit;
                    out("%s,max_high[%s] > old_tp[%s], move sl from[%s] to [%s],profit [%.2f] - [%.2f] :[%.2f]", last.order.getLabel(),
                            ie.formatPrice(lowhigh_diaodeng),ie.formatPrice(tp),
                            last.order.getStopLossPrice(), new_sl,
                            old_profit,new_profit,profit_diff);
                }
                last.order.setStopLossPrice(new_sl);
            }

        } else {
            double tp = tp_maxmin ? lowhigh - tp_price : last_open_price - tp_price;
            double max_diaodeng_yoyo = Math.min(diao_sl_price, yoyo_sl_price);
            double new_sl;
            if(tp_change_to_sl){
                new_sl = new BigDecimal(Math.min(tp, max_diaodeng_yoyo)).setScale(ie.scale + 1, RoundingMode.DOWN).doubleValue();
            }else{
                new_sl = new BigDecimal(max_diaodeng_yoyo).setScale(ie.scale + 1, RoundingMode.DOWN).doubleValue();
            }
            if (lowhigh_diaodeng < tp && new_sl < last.order.getStopLossPrice() && new_sl < last.order.getOpenPrice()) {
                if (log_order_move) {
                    double old_profit = (last.order.getOpenPrice() - tp) * last.order.getAmount()*ie.pipMulti*100;
                    double new_profit = (last.order.getOpenPrice() - new_sl) * last.order.getAmount()*ie.pipMulti*100;
                    double profit_diff =  new_profit - old_profit;
                    out("%s,min_low[%s] < old_tp[%s],  move sl from[%s] to [%s],profit [%.2f] - [%.2f] :[%.2f]", last.order.getLabel(),
                            ie.formatPrice(lowhigh_diaodeng),ie.formatPrice(tp),
                            last.order.getStopLossPrice(), new_sl,old_profit,new_profit,profit_diff);
                }
                last.order.setStopLossPrice(new_sl);
            }
        }
    }

    //从最低位回调
    private void updateTakeProfit(
            AsuraConfig x,      //配置
            InstrumentEntity ie, //交易品种属性
            TradeDirection tradeDirection,
            OrderEx<AsuraOrderComment> last      //尾仓
    ) {
        Instrument instrument = x.instrument;
        ForexConfigSeriesDTO sm_current = series.list.get(last.seriesIndex);
        double lowhigh = tradeDirection == TradeDirection.LONG ? getLowPrice(instrument, last.orderComment.first_time.getTime(), calsPeriod) : getHighPrice(instrument, last.orderComment.first_time.getTime(), calsPeriod);
        double old_tp = last.order.getTakeProfitPrice();
        double old_open_price = last.orderComment.open_price;
        double avg_price = last.order.getOpenPrice();
        double atr_day = atrMap.get(instrument); //atr 这里拿到的是日ATR
        //移动止盈价
        try {
            if (tradeDirection == TradeDirection.LONG) {
                if (lowhigh < old_open_price) {
                    double new_tp_price = atr_day * sm_current.backpip / 100;
                    double new_tp = new BigDecimal(lowhigh + new_tp_price).setScale(ie.scale + 1, RoundingMode.UP).doubleValue();
                    if (new_tp < avg_price) {
                        new_tp = avg_price;
                    }
                    if (new_tp < old_tp) {
                        if (log_order_move) {
                            out("change %s %s tp from[%s] to [%s],exp_profit from[%.2f] to [%.2f]", last.order.getLabel(), last.order.getAmount(), old_tp, new_tp,
                                    (old_tp - last.order.getOpenPrice()) * ie.pipMulti * last.order.getAmount() * 100,
                                    (new_tp - last.order.getOpenPrice()) * ie.pipMulti * last.order.getAmount() * 100);
                        }
                        last.order.setTakeProfitPrice(new_tp);
                    }
                }
            } else {
                if (lowhigh > old_open_price) {
                    double new_tp_price = atr_day * sm_current.backpip / 100;
                    double new_tp = new BigDecimal(lowhigh - new_tp_price).setScale(ie.scale + 1, RoundingMode.DOWN).doubleValue();
                    if (new_tp > avg_price) {
                        new_tp = avg_price;
                    }
                    if (new_tp > old_tp) {
                        if (log_order_move) {
                            out("change %s %s tp from[%s] to [%s],exp_profit from[%.2f] to [%.2f]", last.order.getLabel(), last.order.getAmount(), old_tp, new_tp,
                                    (last.order.getOpenPrice() - old_tp) * ie.pipMulti * last.order.getAmount() * 100,
                                    (last.order.getOpenPrice() - new_tp) * ie.pipMulti * last.order.getAmount() * 100);
                        }
                        last.order.setTakeProfitPrice(new_tp);
                    }
                }
            }
        } catch (JFException e) {
            err("update new tp error:");
        }
    }

    //开首仓
    private void openFirstOrder(
            AsuraConfig x,     //配置
            ITick tick,         //最后一次报价
            TradeDirection tradeDirection,    //交易方向
            OrderEx reflectLast   //反向订单尾仓
    ) {
        //如果上一次止损了，那么必须产生新的压力支撑位
        if (last_sl_new_ylzc) {
            IOrder order = getLastOrder(x, tradeDirection, 60);
            if (order != null) {
                if (order.getProfitLossInUSD() < 0) {
                    //在首仓开单时间到当前时间之间，查找压力支撑位,找到才能继续做单
                    YlzcDTO ylzc = findYlzc(order.getCloseTime(), x.instrument, tradeDirection);
                    if (ylzc == null) {
                        if (log_order_reason && log_first) {
                            out("%s can't open first order ,because from [%s] to [%s] can't find %s",
                                    x.instrument.name(),
                                    CalendarUtils.formatGMTStandard(order.getCloseTime()),
                                    CalendarUtils.formatGMTStandard(context.getTime()),
                                    tradeDirection == TradeDirection.LONG ? "zcw" : "ylw");
                        }
                        return;
                    } else {
                        if (log_ylzc && log_first) {
                            out("%s can open first order ,because from [%s] to [%s] find %s:[%s]",
                                    x.instrument.name(),
                                    CalendarUtils.formatGMTStandard(order.getCloseTime()),
                                    CalendarUtils.formatGMTStandard(context.getTime()),
                                    tradeDirection == TradeDirection.LONG ? "zcw" : "ylw",
                                    ylzc);
                        }
                    }
                }
            }
        }

        //step.1 获取ATR
        Instrument instrument = x.instrument;
        InstrumentEntity ie = insMap.get(instrument);
        //如果点差过大，则不做处理
        double tickDiff = tick.getAsk() - tick.getBid();
        double max_diff = isTestMode() ? x.diff * t_avg_spread : x.diff * r_avg_spread;
        if (tick.getAsk() - tick.getBid() > max_diff) {
            if (log_order_reason)
                out("%s.ask:%s-bid:%s=%s>%s,can't open order", x.instrument.name(), ie.formatPrice(tick.getAsk()), ie.formatPrice(tick.getBid()), ie.formatPrice(tickDiff), ie.formatPrice(max_diff));
            return;
        }
        if (ie == null) {
            return;
        }
        double atr_day = atrMap.get(instrument); //atr 这里拿到的是日ATR
        double atr_k;
        try {
            atr_k = indicators.atr(instrument, this.period, tradeDirection == TradeDirection.LONG ? OfferSide.ASK : OfferSide.BID, this.R_atr_K, 1);//小K线ATR实时计算
        } catch (JFException e) {
            return;
        }
        double total_atr_k = atr_k * x.k_atr;
        double total_atr_d = atr_day * x.d_atr;
        double atr_max = Math.max(total_atr_k, total_atr_d);
        ForexConfigSeriesDTO sm0 = series.get(0);
        if (sm0 == null) {
            return;
        }
        //step.3 找到压力位和支撑位
        double currentPrice = tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();//当前价格
        double calc_ylzc_price = tradeDirection == TradeDirection.LONG ? currentPrice - atr_day * first_loss_limit_position : currentPrice + atr_day * first_loss_limit_position;//计算压力支撑位的真实价格
        YlzcDTO stopLossYlzc = getStopLossYlzc(instrument, currentPrice, calc_ylzc_price, tradeDirection);//找止损位
        if (stopLossYlzc == null) {
            if (log_first && log_order_reason)
                out("can't open first order with no find %s,currentPrice:%s,calc_ylzc_price:%s,total_atr_k=%s,total_atr_d=%s", tradeDirection == TradeDirection.LONG ? "zcw" : "ylw",
                        currentPrice, ie.formatPrice(calc_ylzc_price), ie.formatPrice(total_atr_k), ie.formatPrice(total_atr_d));
            return;
        }
        if (log_ylzc && log_first)
            out("config[%d] %s.%s open first order stop_loss=%s,ylzc=%s", x.index, instrument.name(), tradeDirection.name(), ie.formatPrice(calc_ylzc_price), stopLossYlzc.toString());

        //step.4
        IEngine.OrderCommand orderCommand = tradeDirection == TradeDirection.LONG ? IEngine.OrderCommand.BUY : IEngine.OrderCommand.SELL;
        double distance_real = Math.abs(currentPrice - stopLossYlzc.value);//实际上到达止损位的距离
        double distance_exp = atr_day * R_atr_max_n; //最大期望的抗单距离(日K线*日K倍率)
        if (distance_real > distance_exp) {
            if (log_first && log_order_reason)
                out("config[%d] %s.%s , currentPrice=%s, %s.value=%s , diff=%s>atr[%s]*%f=[%s]",
                        x.index, instrument.name(), tradeDirection.name(), ie.formatPrice(currentPrice), tradeDirection == TradeDirection.LONG ? "zcw" : "ylw", stopLossYlzc, distance_real, ie.formatPrice(atr_day), R_atr_max_n, distance_exp);
            return;
        }
        //step.5 如果找到了止损的压力支撑位，开始设置止损价和止盈价
        double sl = tradeDirection == TradeDirection.LONG ? new BigDecimal(stopLossYlzc.value - stop_loss_atr * atr_day).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(stopLossYlzc.value + stop_loss_atr * atr_day).setScale(ie.scale, RoundingMode.DOWN).doubleValue();
        //计算止盈
        double tp_price = atr_day * sm0.backpip / 100;//回调点位
        double tp = tradeDirection == TradeDirection.LONG ? new BigDecimal(currentPrice + tp_price).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(currentPrice - tp_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();

        double reflect = 1;
        if (reflectManager.valid && reflectLast != null) {
            reflect *= reflectManager.get(reflectLast.seriesIndex);
        }

        double compou = compound ? account.getEquity() / basic_equity : 1;
        if (compound_1 && compou < 1) {
            compou = 1;
        }
        double amount = Math.max(ie.minAmount, ie.minAmount * x.m * reflect * compou);//首仓直接就是用最小仓位*倍率*反向单倍率*复利倍率
        int first_equit = (int) account.getEquity();
        AsuraOrderComment orderComment = new AsuraOrderComment(series.seriesId, 0, OpenType.P, first_equit, context.getTime(), 0, stopLossYlzc.value, currentPrice, currentPrice, currentPrice, 1);
        OrderEx orderEx = new OrderEx(x.name, LabelType.M, x.index, 0, instrument, tradeDirection);
        try {
            if (engine.getOrder(orderEx.toString()) != null) {
                return;
            }
        } catch (JFException e) {
            err("engine.getOrder error:%s", e.getMessage());
        }
        String comment = orderComment.toString();
        long goodTillTime = orderCommand.isConditional() ? context.getTime() + period.getInterval() - Period.ONE_MIN.getInterval() : 0;
        try {
            if (log_first && log_order_submit && log_order_reason) {
                out("ready to submit first order,[%d-%d]-%s.%s,amount=%s,current_price=%s, ylzc=%s,sl=%s,tp=%s,atr_k=%f,atr_d=%f,max=%f",
                        series.seriesId, 0, x.instrument.name(), tradeDirection.name(), amount, currentPrice, stopLossYlzc, sl, tp, atr_k, atr_day, atr_max);
            }
            engine.submitOrder(orderEx.toString(), x.instrument, orderCommand, amount, 0, slippage, sl, tp, goodTillTime, comment);
        } catch (JFException e) {
            warn("config[%d] %s open first order error:%s", x.index, instrument, e.getMessage());
        }
    }

    /**
     * 加仓
     *
     * @param x           Asura配置
     * @param ie          交易品种属性
     * @param tick        最后一次报价
     * @param last        当前尾仓
     * @param reflectLast 反向单尾仓
     */
    private void addOrder(
            AsuraConfig x,      //配置
            InstrumentEntity ie, //交易品种属性
            TradeDirection tradeDirection,
            ITick tick,         //最后一次报价
            OrderEx<AsuraOrderComment> last,       //尾仓
            OrderEx<AsuraOrderComment> reflectLast //反向单尾仓
    ) {
        Instrument instrument = x.instrument;
        int index = last.seriesIndex + 1;
        //step.1 获取ATR
        double atr_day = atrMap.get(instrument); //atr 这里拿到的是日ATR
        double atr_k;
        try {
            atr_k = indicators.atr(instrument, this.period, tradeDirection == TradeDirection.LONG ? OfferSide.ASK : OfferSide.BID, this.R_atr_K, 1);//小K线ATR实时计算
        } catch (JFException e) {
            return;
        }
        double total_atr_k = atr_k * x.k_atr;
        double total_atr_d = atr_day * x.d_atr;
        double atr_max = Math.max(total_atr_k, total_atr_d);
        ForexConfigSeriesDTO sm = series.list.get(index);                                             //本次加仓数列行


        //step.3 计算开仓时的价格,市价单为0
        double current_price = tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();                 //当前价格
        IEngine.OrderCommand orderCommand = tradeDirection == TradeDirection.LONG ? IEngine.OrderCommand.BUYLIMIT : IEngine.OrderCommand.SELLLIMIT;

        double last_price;
        if (last.orderComment.series_index == 0) {
            last_price = last.order.getOpenPrice();//最后一仓开仓的价格
        } else {
            last_price = last.orderComment.open_price;//最后一仓开仓的价格
        }
        double add_price = atr_max * sm.addpip / 100;//当前仓位要求的加仓点距
        double exp_open_price = tradeDirection == TradeDirection.LONG ? last_price - add_price : last_price + add_price;//预期的开仓价格
        String key = CloseRecordDTO.getKey(x.name, x.index, x.instrument, tradeDirection);

        double open_price = tradeDirection == TradeDirection.LONG ? new BigDecimal(exp_open_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue() : new BigDecimal(exp_open_price).setScale(ie.scale, RoundingMode.UP).doubleValue();

        double lowhigh = tradeDirection == TradeDirection.LONG ? getLowPrice(instrument, last.orderComment.first_time.getTime(), calsPeriod) : getHighPrice(instrument, last.orderComment.first_time.getTime(), calsPeriod);
        if (add_maxmin) {
            double new_open_price = tradeDirection == TradeDirection.LONG ? Math.min(lowhigh, open_price) : Math.max(lowhigh, open_price);
            if (isTestMode() && log_order_move &&
                    (
                            (lowhigh < open_price && tradeDirection == TradeDirection.LONG) || (lowhigh > open_price && tradeDirection == TradeDirection.SHORT)
                    )
            ) {
                String submit_str2 = String.format("%s, from[%s] to[%s] %s=%s,old_open_price=%s,new_open_price changed to %s",
                        tradeDirection.name(),
                        dateFormat_yymmddhhmm.format(last.orderComment.first_time),
                        dateFormat_yymmddhhmm.format(context.getTime()),
                        tradeDirection == TradeDirection.LONG ? "min_low" : "max_high",
                        ie.formatPrice(lowhigh),
                        open_price,
                        ie.formatPrice(new_open_price));
                submitStr2.put(key, submit_str2);
            }
            open_price = new_open_price;
        }

        //真实开仓价格必须在首仓找到的压力支撑位以上
        double ylzc = last.orderComment.ylzc;
        if (tradeDirection == TradeDirection.LONG && open_price < ylzc) {
            if (log_order_reason && log_ylzc)
                out("config[%d] %s open %d LONG order, last_price=%s, add_price=%s,open_price=%s < ylzc:%s ,can't add order",
                        x.index, instrument.name(), index, last_price, ie.formatPrice(add_price), open_price, ylzc);
            return;
        }
        if (tradeDirection == TradeDirection.SHORT && open_price > ylzc) {
            if (log_order_reason && log_ylzc)
                out("config[%d] %s open %d SHORT order, last_price=%s, add_price=%s,open_price=%s > ylzc:%s ,can't add order",
                        x.index, instrument.name(), index, last_price, ie.formatPrice(add_price), open_price, ylzc);
            return;
        }

        //step.4 计算上一仓到达开仓点位后要亏损的钱
        double last_order_amount = last.order.getAmount();//上一仓数量
        double last_avg_price = last.order.getOpenPrice();//上一仓的平均价格
        double loss_money = ie.getMoney(tradeDirection == TradeDirection.LONG ? open_price - last_avg_price : last_avg_price - open_price, last_order_amount);//到达开仓点位将会损失的钱
        double loss_charge = ie.getCharge(last_order_amount);
        double real_total_loss = loss_money - loss_charge;//真实亏损（加上手续费）

        double tp_price = atr_day * sm.backpip / 100;//止盈的距离


        //step.5 最复杂的一步，根据盈利钱来倒推计算仓位
        //计算一下要盈利这么多钱，需要多少仓位,先计算需要几个最小仓位
        //原油 1个点 1块钱
        //货币 1个点 0.1块钱
        //黄金 1个点 0.01块钱
        //回调点位15 *  (仓位2=0.002 / 最小仓位1=0.001) * 每个仓位能盈利的钱(0.1) = 3(总盈利的钱)
        //(仓位2=0.002 / 最小仓位1=0.001) = 3/0.1/15 = 2 2就是2倍最小仓位
        //实际仓位 = 仓位倍率*最小仓位 = 3/0.1/15*0.001
        double reflect = 1;
        if (reflectManager.valid && reflectLast != null) {
            reflect *= reflectManager.get(reflectLast.seriesIndex);
        }
        double compou = compound ? account.getEquity() / basic_equity : 1;
        if (compound_1 && compou < 1) {
            compou = 1;
        }
        double exp_tp_money = sm.tp * x.m * reflect * compou;//本次订单希望盈利这么多钱
        double tp_money = exp_tp_money - real_total_loss;//希望盈利的钱-真实亏损的钱= 真正要盈利的钱
        //12.56/0.1/70*0.001 = 125.6/70
        double amount_exp = tp_money / ie.minMoney / (tp_price * ie.pipMulti) * ie.minAmount;//要有这么多仓位，才能在回调这么多点位时，赚到这么多钱

        double amount_open = 0;
        try {
            amount_open = new BigDecimal(amount_exp - last_order_amount).setScale(ie.amountScale, RoundingMode.HALF_UP).doubleValue();//期望的点位-实际的点位=要开仓的点位
        } catch (Throwable e) {
            for (StackTraceElement se : e.getStackTrace()) {
                err("error %s,amount_exp=%s,last_order_amount=%s,tp_money=%s,tp_price=%s,exp_tp_money=%s,real_total_loss=%s,sm.tp=%s", se.toString(), amount_exp, last_order_amount, tp_money, tp_price, exp_tp_money, real_total_loss, sm.tp);
            }
        }
        //做一个四舍五入,怕首仓时不够
        if (amount_open < ie.minAmount) {
            amount_open = ie.minAmount;
        }
        if (log_order_submit && isTestMode()) {
            String submit_str = String.format("current_price=%s," +
                            "add_price[%s]= ( atr[%s]  * add_pip[%d%%] ) ," +
                            "open_price[%s]=(last_price[%s]-add_price[%s])," +
                            "tp_price[%s]=atr_d[%s]*backpip[%d%%]," +
                            "open_amount[%s]=(amount_exp[%s]-current_amount[%s])," +
                            "exp_tp_money[%.2f]=tp[%d]  * compound[%.2f]*reflect[%d-%.1f]," +
                            "tp_money[%.2f]=(exp_tp_money[%.2f] - current_loss=[%.2f]),last_avg_price=%s",
                    current_price,
                    ie.formatPrice(add_price), ie.formatPrice(atr_max), sm.addpip,
                    open_price, last_price, ie.formatPrice(add_price),
                    ie.formatPrice(tp_price), ie.formatPrice(atr_day), sm.backpip,
                    amount_open, ie.formatAmout(amount_exp), ie.formatAmout(last_order_amount),
                    exp_tp_money, sm.tp, compound ? account.getEquity() / basic_equity : 1, reflectLast == null ? 1 : reflectLast.seriesIndex, reflect,
                    tp_money, exp_tp_money, real_total_loss, last_avg_price);
            submitStr.put(key, submit_str);
        }

        //step.6 开仓
        OrderEx orderEx = new OrderEx(x.name, LabelType.N, x.index, index, x.instrument, tradeDirection);
        String label = orderEx.toString();
        try {
            if (engine.getOrder(label) != null) {
                if (log_order_reason) {
                    out("order.label[%s] exist ,can't open order", label);
                }
                return;
            }
        } catch (JFException e) {
            err("getOrder error:%s", e.getMessage());
        }
        AsuraOrderComment orderComment = new AsuraOrderComment(series.seriesId, index, OpenType.P, last.orderComment.first_equit, last.orderComment.first_time.getTime(), (int) real_total_loss, ylzc, last.orderComment.first_open_price, last.orderComment.open_price, open_price, 1);
        String comment = orderComment.toString();
        try {
            long goodTillTime = history.getNextBarStart(period, context.getTime());
            goodTillTime += jiange ? -Period.ONE_SEC.getInterval() * 2 : Period.ONE_SEC.getInterval() * 2;
            engine.submitOrder(label, x.instrument, orderCommand, amount_open, open_price, slippage, 0, 0, goodTillTime, comment);
        } catch (JFException e) {
            if (log_error)
                warn("[label=%s,current_price=%s,open_price=%s,amount_open=%s],error=%s", label, current_price, open_price, ie.formatAmout(amount_open), e.getMessage());
        }
    }

    //找历史订单
    private IOrder getLastOrder(AsuraConfig x, TradeDirection td, int histdays) {
        String key = CloseRecordDTO.getKey(x.name, x.index, x.instrument, td);
        IOrder order = lastCloseOrder.get(key);
        if (order == null) {
            //找历史订单
            try {
                List<IOrder> orderList = history.getOrdersHistory(x.instrument, context.getTime() - Period.DAILY.getInterval() * histdays, context.getTime());//找到60天的历史历史订单
                List<IOrder> histList = orderList.stream()
                        .filter(o -> o.getProfitLossInUSD() != 0
                                && !o.getCloseHistory().isEmpty()
                                && QuantUtil.isMatch(td, o.getOrderCommand())
                                && new OrderEx(o, new AsuraOrderComment()).valid(x.name, x.index))
                        .collect(Collectors.toList());
                if (!histList.isEmpty()) {
                    order = histList.get(histList.size() - 1);
                    lastCloseOrder.put(key, order);
                }
            } catch (JFException e) {
                return null;
            }
        }
        return order;
    }

    //找压力支撑位
    private YlzcDTO findYlzc(long closeTime, Instrument instrument, TradeDirection tradeDirection) {
        YlzcMap ym = ylzcMap.get(instrument);
        if (ym == null) {
            return null;
        }
        List<YlzcDTO> ylzcList = tradeDirection == TradeDirection.LONG ? ym.zcw : ym.ylw;
        if (ylzcList == null) {
            return null;
        }
        for (YlzcDTO ylzc : ylzcList) {
            if (ylzc.thedate.getTime() > closeTime) {
                return ylzc;
            }
        }
        return null;
    }


    /**
     * 整个流程
     * 1. M首仓(带sl,tp) -> 2.N加仓(sl0,tp0) -> 3.修改M仓(sl0,tp0) -> 4.合并同类订单(计算压力支撑位),生成新M - >5.修改新M仓止盈止损价
     */
    @Override
    public void onMessageEx(IMessage message) {
        try {
            IOrder order = message.getOrder();
            if (order == null) {
                return;
            }
            switch (message.getType()) {
                case ORDER_FILL_REJECTED:
                case ORDER_SUBMIT_REJECTED:
                case ORDERS_MERGE_REJECTED:
                case ORDER_CHANGED_REJECTED:
                    if (log_order_reason && log_error) err(message.toString());
                    break;
            }
            String label = order.getLabel();
            if (label == null) {
                return;
            }
            String[] label_arr = label.split("_");
            if (label_arr.length < 1) {
                return;
            }
            if ((getName()).equalsIgnoreCase(label_arr[0])) {
                OrderEx<AsuraOrderComment> orderEx = new OrderEx(order, new AsuraOrderComment());
                //监听逆势仓的单子
                if (!orderEx.valid(getName())) {
                    return;
                }
                ROnMessage(message, orderEx, order);
            }
        } catch (Throwable e) {
            for (StackTraceElement s : e.getStackTrace()) {
                err("on message,error,%s", s.toString());
            }
        }
    }

    private void ROnMessage(IMessage message, OrderEx<AsuraOrderComment> orderEx, IOrder order) {
        //获取订单的基本参数
        InstrumentEntity ie = new InstrumentEntity(order.getInstrument());
        IAccount account = this.context.getAccount();
        AsuraOrderComment orderComment = orderEx.orderComment;
        int first_equit = orderComment.first_equit;
        int current_equity = (int) account.getEquity();
        int index = orderComment.series_index;
        int seriesId = orderComment.series_id;
        LabelType labelType = orderEx.labelType;
        double openPrice = order.getOpenPrice();
        double closePrice = order.getClosePrice();
        double amount = order.getAmount();
        Instrument instrument = order.getInstrument();
        IEngine.OrderCommand orderCommand = order.getOrderCommand();
        String instrumentName = order.getInstrument().name();
        double sl = order.getStopLossPrice();
        double tp = order.getTakeProfitPrice();
        int dist = Math.abs(ie.priceToPip(orderEx.orderComment.first_open_price - orderEx.orderComment.open_price));
        String key = CloseRecordDTO.getKey(orderEx.name, orderEx.configIndex, orderEx.instrument, orderEx.tradeDirection);
        switch (message.getType()) {
            case ORDER_FILL_OK:
                if (log_order_submit && isTestMode()) {
                    if (submitStr.get(key) != null) {
                        super.console.getOut().println(submitStr.get(key));
                    }
                    if (submitStr1.get(key) != null) {
                        super.console.getOut().println(submitStr1.get(key));
                    }

                }
                if (log_order_move && isTestMode()) {
                    if (submitStr2.get(key) != null) {
                        super.console.getOut().println(submitStr2.get(key));
                    }
                }
                if (log_order_merge && sl != 0 && tp != 0) {
                    notif("%s %s, Fill %d-%d Order[%s.%s,amount=%s,order_comment=%s,sl=%s,tp=%s,rate=%s,type=%s]", getName(), getVersion(),
                            seriesId, index, instrumentName, orderCommand, amount, order.getComment(), sl, tp, orderComment.next_add_r, orderComment.type);
                }
                if (log_order_fill && sl == 0 && tp == 0) {
                    out("%s %s, Fill %d-%d Order[%s.%s,amount=%s,open=%s,order_comment=%s,rate=%s,type=%s]", getName(), getVersion(),
                            seriesId, index, instrumentName, orderCommand, amount, openPrice, order.getComment(), orderComment.next_add_r, orderComment.type);
                }
                //如果是加仓订单成交，则修改止盈止损值
                if (labelType == LabelType.N) {
                    cancelSLAndTP(orderEx);
                }
                break;
            case ORDER_CLOSE_OK:
                if (order.getState() == IOrder.State.CLOSED) {
                    closeLimit(orderEx);
                    if (log_order_reason && log_order_merge && message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_MERGE)) {
                        out("%s %s, Close by merge %d-%d Order[%s.%s ,amount=%s,open=%s]", getName(), getVersion(),
                                seriesId, index, instrumentName, orderCommand, amount, openPrice);
                    }
                    if (order.getProfitLossInUSD() > 0) {
                        tp_sum += order.getProfitLossInUSD();
                        if (log_order_close) {
                            info("%s %s, %s,Close by TP %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],total_tp=%.2f", getName(), getVersion(),
                                    order.getProfitLossInUSD(), seriesId, index, instrumentName, orderCommand.name(), amount, openPrice, sl, tp, closePrice, tp_sum);
                        }
                        CloseRecordDTO closeRecordDTO = new CloseRecordDTO(orderEx, CloseType.TP, context.getTime());
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                        lastCloseOrder.put(closeRecordDTO.key, order);//记录最后一个平仓订单
                    }
                    if (order.getProfitLossInUSD() < 0) {
                        sl_sum += order.getProfitLossInUSD();
                        if (log_order_close) {
                            err("%s %s, %s, Close by SL %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],total_sl=%.2f", getName(), getVersion(),
                                    order.getProfitLossInUSD(), seriesId, index, instrumentName, orderCommand.name(), amount, openPrice, sl, tp, closePrice, sl_sum);
                        }
                        CloseRecordDTO closeRecordDTO = new CloseRecordDTO(orderEx, CloseType.SL, context.getTime());
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                        lastCloseOrder.put(closeRecordDTO.key, order);//记录最后一个平仓订单
                    }
                    /*
                    if (log_order_submit && order.getProfitLossInUSD() != 0) {
                        try {
                            for (IOrder o : engine.getOrders()) {
                                out("-->exist.order,%s %s %s,amount=%s,sl=%s,tp=%s,profit=%s,opentime=%s,filltime=%s,comment=%s",
                                        o.getInstrument().name(), o.getOrderCommand().name(), o.getLabel(), ie.formatAmout(o.getAmount()), o.getStopLossPrice(), o.getTakeProfitPrice(), o.getProfitLossInUSD(),
                                        CalendarUtils.formatGMTStandard(o.getCreationTime()), CalendarUtils.formatGMTStandard(o.getFillTime()), o.getComment());
                            }
                        } catch (JFException e) {
                            e.printStackTrace();
                        }
                    }*/
                } else if (order.getState() == IOrder.State.CANCELED) {
                    if (log_order_cancel) {
                        out("%s %s, Cancel Order %d-%d %s [%s]", getName(), getVersion(), orderEx.orderComment.series_id, orderEx.seriesIndex, orderCommand.name(), order.getComment());
                    }
                }
                break;
            case ORDERS_MERGE_OK:
                double ylzc = orderEx.orderComment.ylzc;//首仓记录的压力支撑价
                double atr = atrMap.get(order.getInstrument());

                double newSL = orderCommand == IEngine.OrderCommand.BUY ? new BigDecimal(ylzc - atr * stop_loss_atr).setScale(ie.scale, RoundingMode.DOWN).doubleValue() : new BigDecimal(ylzc + atr * stop_loss_atr).setScale(ie.scale, RoundingMode.UP).doubleValue();
                double open_price = orderEx.orderComment.open_price;//开仓价
                ForexConfigSeriesDTO sm = series.get(orderEx.seriesIndex);
                if (sm == null) {
                    break;
                }
                if (!atrMap.containsKey(instrument)) {
                    break;
                }
                double atr_day = atrMap.get(instrument); //atr 这里拿到的是大K线ATR
                double tp_price = atr_day * sm.backpip / 100;
                double newTP = orderCommand == IEngine.OrderCommand.BUY ? new BigDecimal(open_price + tp_price).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(open_price - tp_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();
                try {
                    order.setStopLossPrice(newSL);
                    if (diaodeng == 0) {
                        order.setTakeProfitPrice(newTP);
                    }
                } catch (JFException e) {
                    err("order.setTakeProfitPrice error:%s", e.getMessage());
                }
                if (log_order_merge) {
                    notif("%s %s,%s, Merge  %d-%d Order[%s.%s,amount=%s,open_price=%s, avg_price=%s,sl=%s,tp=%s],comment=%s",
                            getName(), getVersion(), order.getProfitLossInUSD(), seriesId, index, instrumentName, orderCommand, amount, orderEx.orderComment.open_price, openPrice, newSL, newTP, order.getComment());
                }
                break;
            case ORDER_CHANGED_OK:
                //修改止盈止损价成功,判断是不是M仓的止盈止损价都修改为0了
                if (orderEx.labelType == LabelType.M && sl == 0 && tp == 0) {
                    if (log_order_merge && log_order_reason) {
                        out("%s %s, %d-%d,Merge sl and tp by Order[%s.%s labelType=%s] ", getName(), getVersion(), seriesId, index, instrumentName, orderCommand, orderEx.labelType);
                    }
                    //进行合并仓位
                    merge(order.getInstrument(), ie, orderEx.name, orderEx.configIndex, orderEx.tradeDirection);
                }
                break;
            case ORDER_SUBMIT_OK:
                if (log_order_submit && isTestMode()) {
                    ITick tick;
                    try {
                        tick = history.getLastTick(order.getInstrument());
                        double currentPrice = orderEx.tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();
                        if (isTestMode() && log_order_submit) {
                            String submit_str1 = String.format("%s %s, Submit %d-%d Order[%s.%s ,amount=%s,current_price=%s,open_price=%s,goodTillTime=%s],id=%s,label=%s", getName(), getVersion(),
                                    seriesId, index, instrumentName, orderCommand, amount, currentPrice, order.getOpenPrice(), order.getGoodTillTime() == 0 ? "0" : CalendarUtils.formatStandard(order.getGoodTillTime()), order.getId(), order.getLabel());
                            submitStr1.put(key, submit_str1);
                        }
                    } catch (JFException e) {
                        err("history.getLastTick error:%s", e.getMessage());
                    }
                }
                break;
            default:
                break;
        }

    }

    //2.修改M仓
    private void cancelSLAndTP(OrderEx orderExNew) {
        //获取同类的M仓
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(orderExNew.instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> sameMList = getSameOrders(orderList, orderExNew.instrument, orderExNew.name, orderExNew.configIndex, orderExNew.tradeDirection, LabelType.M, AsuraOrderComment.class);
        updateSLTP(sameMList, 0, 0);//将所有价格修改为0
    }

    private Lock mergeLock = new ReentrantLock();

    //3.合并N和M仓
    private void merge(Instrument instrument, InstrumentEntity ie, String name, int configIndex, TradeDirection tradeDirection) {
        if (!mergeLock.tryLock()) {
            return;
        }
        mergeLock.lock();
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> mergeOrderExList = getSameOrders(orderList, instrument, name, configIndex, tradeDirection, null, AsuraOrderComment.class);
        List<OrderEx> invalidOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() != 0 || x.order.getStopLossPrice() != 0)
                .collect(Collectors.toList());
        if (!invalidOrderList.isEmpty()) {
            updateSLTP(invalidOrderList, 0, 0);
            return;
        }
        List<IOrder> mergeOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() == 0 && x.order.getStopLossPrice() == 0)
                .map(x -> x.order)
                .collect(Collectors.toList());
        //如果需要合并的订单只有1个，则放弃merge
        if (mergeOrderExList.size() < 2) {
            return;
        }
        OrderEx<AsuraOrderComment> last_N = mergeOrderExList.get(mergeOrderExList.size() - 1); //N仓（最新的仓）
        OrderEx<AsuraOrderComment> orderEx = new OrderEx(last_N.name, LabelType.M, last_N.configIndex, last_N.seriesIndex, last_N.instrument, last_N.tradeDirection);
        String label = orderEx.toString();
        try {
            if (engine.getOrder(label) != null) {
                return;
            }
        } catch (JFException e) {
            err("engine.getOrder error:%s", e.getMessage());
        }
        int series_index = last_N.orderComment.series_index;
        int series_id = last_N.orderComment.series_id;
        ForexConfigSeriesDTO sm_next = series.get(series_index + 1);

        //需要自己算的就是跟下一仓有关的
        int first_equit = last_N.orderComment.first_equit;
        Date first_time = last_N.orderComment.first_time;
        int this_profitloss = last_N.orderComment.this_profitloss;
        double ylzc = last_N.orderComment.ylzc;
        double first_open_price = last_N.orderComment.first_open_price;
        double pre_open_price = last_N.orderComment.pre_open_price;
        double current_open_price = last_N.order.getOpenPrice();
        OpenType openType = OpenType.P;
        if (sm_next == null) {
            //如果加到尾仓，则进行止损
            openType = OpenType.L;
        }
        //下面两个数值，
        AsuraOrderComment orderComment = new AsuraOrderComment(series_id, series_index, openType, first_equit, first_time.getTime(), this_profitloss, ylzc, first_open_price, pre_open_price, current_open_price, 1);
        String comment = orderComment.toString();
        try {
            IOrder order = engine.mergeOrders(label, comment, mergeOrderList);
            order.waitForUpdate(2000, IOrder.State.FILLED);
        } catch (JFException e) {
            warn("config[%d] %s.%s merge order[%s]-[%s]:%s", configIndex, instrument.name(), tradeDirection.name(), label, comment, e.getMessage());
        }
        mergeLock.unlock();
    }

    //找止损位
    private YlzcDTO getStopLossYlzc(Instrument instrument, double currentPrice, double open_price, TradeDirection tradeDirection) {
        YlzcMap ym = ylzcMap.get(instrument);
        if (ym == null) {
            return null;
        }
        return getStopLossYlzcWithAI1(instrument, ym, currentPrice, open_price, tradeDirection);
    }

    private YlzcDTO getStopLossYlzcWithAI1(Instrument instrument, YlzcMap ym, double currentPrice, double open_price, TradeDirection tradeDirection) {
        Double atr_day = atrMap.get(instrument);
        List<YlzcDTO> ylzcList = tradeDirection == TradeDirection.LONG ? ym.zcw : ym.ylw;
        if (ylzcList == null || atr_day == null || ylzcList.size() < 0) {
            return null;
        }
        YlzcDTO resultYlzcDTO = null;
        int r_num = 0;
        //倒序找
        for (int i = ylzcList.size() - 1; i >= 0; i--) {
            YlzcDTO ylzc = ylzcList.get(i);
            double diff = Math.abs(currentPrice - ylzc.value);
            if (tradeDirection == TradeDirection.LONG) {
                if (resultYlzcDTO == null) {
                    if (ylzc.value.doubleValue() < open_price) {
                        resultYlzcDTO = ylzc;//找到第一个比open_price小的支撑位
                    }
                } else {
                    //再已经找到一个支撑位的情况下，接下来就判断上一个支撑位的位置比当前的高还是低
                    if (ylzc.value < resultYlzcDTO.value) {
                        r_num = 0;
                        if (diff > atr_day * R_atr_max_n) {
                            break;
                        }
                        resultYlzcDTO = ylzc;
                    } else {
                        r_num += 1;
                        if (r_num > this.AI_MAX_k) {
                            break;
                        }
                    }
                }

            } else {
                if (resultYlzcDTO == null) {
                    if (ylzc.value.doubleValue() > open_price) {
                        resultYlzcDTO = ylzc;//找到第一个比open_price小的支撑位
                    }
                } else {
                    //再已经找到一个支撑位的情况下，接下来就判断上一个支撑位的位置比当前的高还是低
                    if (ylzc.value > resultYlzcDTO.value) {
                        r_num = 0;
                        if (diff > atr_day * R_atr_max_n) {
                            break;
                        }
                        resultYlzcDTO = ylzc;
                    } else {
                        r_num += 1;
                        if (r_num < this.AI_MAX_k) {
                            break;
                        }
                    }
                }
            }
        }

        //如果确定了找最低价
        if (AI_LIMIT && resultYlzcDTO != null) {
            if (tradeDirection == TradeDirection.LONG) {
                List<TimeBar> barList = bidBarMap.get(instrument);
                for (int i = 0; i < barList.size(); i++) {
                    TimeBar timeBar = barList.get(i);
                    if (timeBar.time > resultYlzcDTO.thedate.getTime()
                            && timeBar.low < resultYlzcDTO.value) {
                        Date date = new Date(timeBar.time);
                        if (log_ylzc)
                            out("ylzc:%s,new low:[%s-%s]", resultYlzcDTO.toString(), CalendarUtils.formatGMTStandard(date), timeBar.low);
                        resultYlzcDTO.thedate = date;
                        resultYlzcDTO.value = timeBar.low;
                    }
                }
            } else {
                List<TimeBar> askList = askBarMap.get(instrument);
                for (int i = 0; i < askList.size(); i++) {
                    TimeBar timeBar = askList.get(i);
                    if (timeBar.time > resultYlzcDTO.thedate.getTime()
                            && timeBar.high > resultYlzcDTO.value) {
                        Date date = new Date(timeBar.time);
                        if (log_ylzc)
                            out("ylzc:%s,new high:[%s-%s]", resultYlzcDTO.toString(), CalendarUtils.formatGMTStandard(date), timeBar.high);
                        resultYlzcDTO.thedate = new Date(timeBar.time);
                        resultYlzcDTO.value = timeBar.high;
                    }
                }
            }
        }
        //如果确定压力支撑位的反转
        if (AI_ylzc_R) {
            if (tradeDirection == TradeDirection.LONG) {
                List<YlzcDTO> ylwList = ym.ylw;
                YlzcDTO maxYlw = null;
                for (int i = 0; ylwList != null && i < ylwList.size(); i++) {
                    YlzcDTO ylw = ylwList.get(i);
                    if (maxYlw == null) {
                        maxYlw = ylw;
                    } else if (ylw.value > maxYlw.value) {
                        maxYlw = ylw;
                    }
                }
                if (maxYlw != null && currentPrice > maxYlw.value) {
                    if (log_ylzc) out(" currentPrice:%s  > max ylw:%s", currentPrice, maxYlw.toString());
                    resultYlzcDTO = maxYlw;
                }
            } else {
                List<YlzcDTO> zcwList = ym.zcw;
                YlzcDTO minZcw = null;
                for (int i = 0; zcwList != null && i < zcwList.size(); i++) {
                    YlzcDTO zcw = zcwList.get(i);
                    if (minZcw == null) {
                        minZcw = zcw;
                    } else if (zcw.value < minZcw.value) {
                        minZcw = zcw;
                    }
                }
                if (minZcw != null && currentPrice < minZcw.value) {
                    if (log_ylzc) out("currentPrice:%s < min zwc:%s", currentPrice, minZcw.toString());
                    resultYlzcDTO = minZcw;
                }
            }
        }
        return resultYlzcDTO;
    }


    //删除同方向的挂单
    private void closeLimit(OrderEx closeOrderEx) {
        try {
            List<IOrder> orderList = engine.getOrders(closeOrderEx.instrument);
            for (IOrder order : orderList) {
                OrderEx orderEx = new OrderEx(order, new AsuraOrderComment());
                if (!orderEx.valid(closeOrderEx.name, closeOrderEx.configIndex)) {
                    continue;
                }
                TradeDirection tradeDirection = closeOrderEx.tradeDirection;
                IEngine.OrderCommand o2 = order.getOrderCommand();
                if ((tradeDirection == TradeDirection.LONG && o2 == IEngine.OrderCommand.BUYLIMIT)
                        || (tradeDirection == TradeDirection.SHORT && o2 == IEngine.OrderCommand.SELLLIMIT)) {
                    if (order.getState() == IOrder.State.OPENED) {
                        order.close();
                        order.waitForUpdate(500, IOrder.State.CANCELED);
                    }
                }
            }
        } catch (JFException e) {
            if (log_error)
                out("config[%d] close %s Limit error:", closeOrderEx.configIndex, closeOrderEx.tradeDirection.name());
        }
    }

    @Override
    public void onAccountEx(IAccount account) {
    }

    //加仓数列
    final class Series {
        final int seriesId;//加仓数列ID
        List<ForexConfigSeriesDTO> list;
        int totalPip;//总

        Series(int seriesId) {
            this.seriesId = seriesId;
            ResultDTO<List<ForexConfigSeriesDTO>> resultDTO = forexServiceProxy.findConfigSeries(seriesId);
            if (!resultDTO.okData() || resultDTO.data.isEmpty()) {
                return;
            }
            list = resultDTO.data.stream().filter(x -> x.backpip != null && x.backpip != 0 && x.tp != null && x.tp != 0).collect(Collectors.toList());
//            list.addAll(resultDTO.data);
            for (ForexConfigSeriesDTO seriesDTO : list) {
                totalPip += seriesDTO.addpip;
            }
        }

        @Override
        public String toString() {
            return "ATRSeries,series.length" + list.size();
        }

        ForexConfigSeriesDTO get(int index) {
            return index < 0 || index > list.size() - 1 ? null : list.get(index);
        }

        int size() {
            return list.size();
        }
    }

    //阿波罗逆市单配置
    final static class AsuraConfig extends BaseConfig {
        final Instrument instrument;//货币
        final double diff;//货币点差
        final double k_atr;//k线atr倍率
        final double d_atr;//日线atr倍率
        final TradeDirection tradeDirection;//boll倍率

        AsuraConfig(String name, int index, double m, double lossLimit, Instrument instrument, double diff, double k_atr, double d_atr, String tradeDirection) {
            super(name, index, m, lossLimit);
            this.instrument = instrument;
            this.diff = diff;
            this.k_atr = k_atr;
            this.d_atr = d_atr;
            this.tradeDirection = TradeDirection.parse(tradeDirection);
        }

        @Override
        public String toString() {
            return "Asura_" + index
                    + "_" + instrument.name()
                    + "_o_" + tradeDirection.name()
                    + "_m_" + m
                    + "_diff_" + diff
                    + "k_atr" + k_atr
                    + "d_atr" + d_atr;
        }

        @Override
        public boolean isValid() {
            return instrument != null && m > 0 && diff > 0 && k_atr > 0 && d_atr > 0 && tradeDirection != null;
        }
    }

    /**
     * 数列id_第几仓_加仓类型_首仓净值_首仓时间_首仓价格_下一次加仓倍率
     */
    final static class AsuraOrderComment extends BaseOrderComment {
        double ylzc;                //本次加仓的用于止损的压力支撑价格6
        double first_open_price;    //首仓price   7
        double pre_open_price;      //上一仓price 8
        double open_price;          //当前仓price 9
        double next_add_r;          //下一次加仓点位倍率 10

        AsuraOrderComment() {
            super();
        }

        public void parse(String comment) {
            super.parse(comment);
            if (s.length >= getParamNum()) {
                try {
                    ylzc = Double.parseDouble(s[6]);
                    first_open_price = Double.parseDouble(s[7]);
                    pre_open_price = Double.parseDouble(s[8]);
                    open_price = Double.parseDouble(s[9]);
                    next_add_r = Double.parseDouble(s[10]);
                    valid = true;
                } catch (Exception e) {
                    valid = false;
                }
            }
        }

        AsuraOrderComment(int series_id, int series_index, OpenType type, int first_equit, long first_time, int this_profitloss, double ylzc, double first_open_price, double pre_open_price, double open_price, double next_add_r) {
            super(series_id, series_index, type, first_equit, first_time, this_profitloss);
            this.ylzc = ylzc;
            this.first_open_price = first_open_price;
            this.pre_open_price = pre_open_price;
            this.open_price = open_price;
            this.next_add_r = next_add_r;
        }

        public String toString() {
            return String.format("%s_%s_%s_%s_%s_%s", super.toString(), ylzc, first_open_price, pre_open_price, open_price, next_add_r);
        }

        @Override
        public int getParamNum() {
            return super.getParamNum() + 5;
        }
    }

    private static List<OrderEx> getSameOrders(List<IOrder> orderList, Instrument instrument, String name, int configIndex,
                                               TradeDirection tradeDirection, LabelType labelType, Class<? extends BaseOrderComment> c) {
        List<OrderEx> result = new ArrayList<>();
        //先遍历所有的订单，找到与当前订单类似的
        for (IOrder order : orderList) {
            if (order == null || order.getInstrument() != instrument || order.getState() != IOrder.State.FILLED) {
                continue;
            }
            BaseOrderComment orderComment;
            if (AsuraOrderComment.class.equals(c)) {
                orderComment = new AsuraOrderComment();
            } else {
                continue;
            }
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(name, configIndex) || tradeDirection != orderEx.tradeDirection) {
                continue;
            }
            if (labelType != null && labelType != orderEx.labelType) {
                continue;
            }
            result.add(orderEx);
        }
        //result.sort((a, b) -> a.seriesIndex - b.seriesIndex);//按订单序列号排序
        //result.sort(Comparator.comparing(OrderEx::getSeriesIndex));
        result.sort(Comparator.comparingInt(x -> x.seriesIndex));
        return result;
    }

    private static OrderExManager getOrderExManager(BaseConfig baseConfig, List<IOrder> orderList, Class<? extends BaseOrderComment> c) {

        final OrderExManager o = new OrderExManager();
        for (IOrder order : orderList) {
            BaseOrderComment orderComment;
            if (AsuraOrderComment.class.equals(c)) {
                orderComment = new AsuraOrderComment();
            } else {
                continue;
            }
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(baseConfig.name, baseConfig.index) || !orderEx.orderComment.valid) {
                continue;
            }

            if (order.getOrderCommand() == IEngine.OrderCommand.BUY && order.getState() == IOrder.State.FILLED) {
                o.buyList.add(orderEx);
                o.buy_pl_usd += order.getProfitLossInUSD();
                o.buy_pl_pip += order.getProfitLossInPips();
                o.buy_c += order.getCommissionInUSD();
                if (o.buy_first == null || orderEx.seriesIndex < o.buy_first.seriesIndex) {
                    o.buy_first = orderEx;
                }
                if (o.buy_last == null || orderEx.seriesIndex > o.buy_last.seriesIndex) {
                    o.buy_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELL && order.getState() == IOrder.State.FILLED) {
                o.sellList.add(orderEx);
                o.sell_pl_usd += order.getProfitLossInUSD();
                o.sell_pl_pip += order.getProfitLossInPips();
                o.sell_c += order.getCommissionInUSD();
                if (o.sell_first == null || orderEx.seriesIndex < o.sell_first.seriesIndex) {
                    o.sell_first = orderEx;
                }
                if (o.sell_last == null || orderEx.seriesIndex > o.sell_last.seriesIndex) {
                    o.sell_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLLIMIT && order.getState() == IOrder.State.OPENED) {
                o.sellLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYLIMIT && order.getState() == IOrder.State.OPENED) {
                o.buyLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYSTOP && order.getState() == IOrder.State.OPENED) {
                o.buyStopList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLSTOP && order.getState() == IOrder.State.OPENED) {
                o.sellStopList.add(orderEx);
            }
        }
        return o;
    }
}
